/**
 *
 */
package com.wedroid.r2d2.http;

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;

import javax.net.ssl.SSLContext;

import org.apache.http.Header;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.CookieStore;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLInitializationException;
import org.apache.http.cookie.Cookie;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * @author lute
 */
@Component
public class DroidHttpClient {

    private final Logger logger = LoggerFactory.getLogger(DroidHttpClient.class);

    private static final String USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36";

    private static final PoolingHttpClientConnectionManager MANAGER;
    private static final HttpContext CONTEXT;
    private static final CloseableHttpClient CLIENT;
    private static final CookieStore STORE;

    private static final IdleConnectionMonitor MONITOR;

    static {
        LayeredConnectionSocketFactory ssl = null;
        try {
            ssl = SSLConnectionSocketFactory.getSystemSocketFactory();
        } catch (final SSLInitializationException ex) {
            final SSLContext sslcontext;
            try {
                sslcontext = SSLContext.getInstance(SSLConnectionSocketFactory.TLS);
                sslcontext.init(null, null, null);
                ssl = new SSLConnectionSocketFactory(sslcontext);
            } catch (final SecurityException ignore) {
            } catch (final KeyManagementException ignore) {
            } catch (final NoSuchAlgorithmException ignore) {
            }
        }

        final Registry<ConnectionSocketFactory> sfr = RegistryBuilder.<ConnectionSocketFactory>create()
            .register("http", PlainConnectionSocketFactory.getSocketFactory())
            .register("https", ssl != null ? ssl : SSLConnectionSocketFactory.getSocketFactory())
            .build();

        MANAGER = new PoolingHttpClientConnectionManager(sfr);
        MANAGER.setDefaultMaxPerRoute(100);
        MANAGER.setMaxTotal(200);
        MANAGER.setValidateAfterInactivity(3000);

        Header userAgent = new BasicHeader(HttpHeaders.USER_AGENT, USER_AGENT);
        Header accept = new BasicHeader(HttpHeaders.ACCEPT, "application/json, text/plain, */*");

        STORE = new BasicCookieStore();
        CONTEXT = new BasicHttpContext();
        HttpClientContext.adapt(CONTEXT).setCookieStore(STORE);

        RequestConfig config = RequestConfig.custom()
                                            .setSocketTimeout(60 * 5 * 1000)
                                            .setConnectTimeout(60 * 5 * 1000)
                                            .build();
        ConnectionKeepAliveStrategy strategy = new ConnectionKeepAliveStrategy() {
            @Override
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                // Honor 'keep-alive' header
                HeaderElementIterator iterator = new BasicHeaderElementIterator(
                        response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (iterator.hasNext()) {
                    HeaderElement headerElement = iterator.nextElement();
                    String name = headerElement.getName();
                    String value = headerElement.getValue();
                    if (value != null && name.equalsIgnoreCase("timeout")) {
                        try {
                            return Long.parseLong(value) * 1000;
                        } catch(final NumberFormatException ignore) {
                        }
                    }
                }
                HttpHost target = (HttpHost) context.getAttribute(
                        HttpClientContext.HTTP_TARGET_HOST);
                if (target.getHostName().endsWith("qq.com")) {
                    // Keep alive for 5 seconds only
                    return 5 * 1000;
                }
                // Otherwise keep alive for 60 seconds
                return 60 * 1000;
            }
        };

        CLIENT = HttpClientBuilder.create()
                    .setConnectionManager(MANAGER)
                    .setDefaultHeaders(Arrays.asList(userAgent, accept))
                    .setDefaultCookieStore(STORE)
                    .setDefaultRequestConfig(config)
                    .disableRedirectHandling()
                    .setRetryHandler(new DefaultHttpRequestRetryHandler(3, false))
                    .setKeepAliveStrategy(strategy)
                    .build();

        MONITOR = new IdleConnectionMonitor(MANAGER);
        MONITOR.start();
    }

    public void shutdown() {
        MONITOR.shutdown();
    }

    /**
     * @param <T>
     * @param request
     * @param responseHandler
     * @return
     */
    public <T> T execute(final HttpUriRequest request, final ResponseHandler<? extends T> responseHandler) {
        try {
            return CLIENT.execute(request, responseHandler, CONTEXT);
        } catch (ClientProtocolException e) {
            logger.error("#execute - Executing request [{}] encounters ClientProtocolException: {}", request, e);
            throw new RuntimeException(e);
        } catch (IOException e) {
            logger.error("#execute - Executing request [{}] encounters IOException: {}", request, e);
            throw new RuntimeException(e);
        }
    }

    /**
     * @param request
     * @return
     */
    public CloseableHttpResponse execute(final HttpUriRequest request) {
        try {
            return CLIENT.execute(request);
        } catch (ClientProtocolException e) {
            logger.error("#execute - Executing request [{}] encounters ClientProtocolException: {}", request, e);
        } catch (IOException e) {
            logger.error("#execute - Executing request [{}] encounters IOException: {}", request, e);
        }
        return null;
    }

    /**
     * @return the client
     */
    public static final CloseableHttpClient getClient() {
        return CLIENT;
    }

    /**
     * @param name
     * @return
     */
    public Optional<String> getCookie(String name) {
        List<Cookie> cookies = STORE.getCookies();
        for (Cookie cookie : cookies) {
            if (cookie.getName().equals(name)) {
                return Optional.of(cookie.getValue());
            }
        }
        return Optional.empty();
    }

}
